﻿using SoldiHive.Core.Parsing;
using SoldiHive.Model;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;

namespace SoldiHive.Core.Persistence
{
	public class QuotesRepository : IQuoteRepository
	{
		private static object locker = new object();

		private class QuotesInfo
		{
			public Company Company { get; set; }
			public IList<Quote> Quotes { get; set; }
		}

		private IQuoteRepositoryConfiguration configuration;
		private IParser parser;
		private ICompanyRepository companyRepository;

		private BlockingCollection<QuotesInfo> quotesInfo;

		public QuotesRepository(IParser parser, ICompanyRepository companyRepository, IQuoteRepositoryConfiguration configuration)
		{
			this.configuration = configuration;
			this.parser = parser;
			this.companyRepository = companyRepository;

			this.quotesInfo = new BlockingCollection<QuotesInfo>();
		}

		public void LoadQuotes()
		{
			var quotesDirectory = this.configuration.QuotesDirectory;

			this.LoadQuotes(quotesDirectory);
		}

		private void LoadQuotes(string quotesDirectory)
		{
			var quotesFiles = Directory.GetFiles(quotesDirectory);
			foreach (var quotesFile in quotesFiles)
			{
				var ticker = Path.GetFileNameWithoutExtension(quotesFile);
				var company = new Company(ticker);

				this.LoadQuotes(company, quotesFile);
			}
		}

		public void LoadQuotes(Company company, string quotesFileName)
		{
			using (var file = File.Open(quotesFileName, FileMode.Open, FileAccess.Read))
			using (var reader = new StreamReader(file))
			{
				this.LoadQuotes(company, reader);
			}
		}

		public void LoadQuotes(Company company, StreamReader reader)
		{
			var quotes = this.ReadQuotes(reader);
			if (this.quotesInfo.Any(info => info.Company == company))
			{
				var quotesInfoItem = this.quotesInfo.Single(info => info.Company == company);
				quotesInfoItem.Quotes = quotes;
			}
			else
			{
				this.quotesInfo.Add(new QuotesInfo()
				{
					Company = company,
					Quotes = quotes
				});
			}
		}

		private IList<Quote> ReadQuotes(StreamReader reader)
		{
			var quotes = new List<Quote>();

			string line;
			while (!string.IsNullOrEmpty((line = reader.ReadLine())))
			{
				var values = line.Split(",".ToCharArray(), 7, StringSplitOptions.None)
					.Select(s => s.Trim())
					.ToArray();

				var date = this.parser.ParseDate(values[0]);
				var open = this.parser.ParseDouble(values[1]);
				var high = this.parser.ParseDouble(values[2]);
				var low = this.parser.ParseDouble(values[3]);
				var close = this.parser.ParseDouble(values[4]);
				var volume = this.parser.ParseInteger(values[5]);

				if (!date.HasValue || !open.HasValue || !high.HasValue || !low.HasValue || !close.HasValue || !volume.HasValue)
				{
					continue;
				}

				var quote = new Quote(date.Value, open.Value, high.Value, low.Value, close.Value, volume.Value);
				quotes.Add(quote);
			}

			return quotes;
		}

		public void DownloadQuotes(Company company)
		{
			var quotesFileName = this.GetQuotesFileName(company);
			if (this.QuotesFileAlreadyExists(company))
			{
				File.Delete(quotesFileName);
			}

			var address = string.Format("http://ichart.finance.yahoo.com/table.csv?s={0}", company.Ticker);

			using (WebClient webClient = new WebClient())
			{
				try
				{
					webClient.DownloadFile(address, quotesFileName); 
				}
				catch (WebException)
				{
					if (!this.quotesInfo.Any(info => info.Company == company))
					{
						this.quotesInfo.Add(new QuotesInfo()
						{
							Company = company,
							Quotes = Enumerable.Empty<Quote>().ToList()
						}); 
					}

					return;
				}
			}

			this.LoadQuotes(company, quotesFileName);
		}

		private bool QuotesFileAlreadyExists(Company company)
		{
			var quotesFileName = this.GetQuotesFileName(company);
			var quotesFileAlreadyExists = File.Exists(quotesFileName);

			return quotesFileAlreadyExists;
		}

		private string GetQuotesFileName(Company company)
		{
			const string quotesFileExtension = ".csv";
			var quotesFileName = Path.Combine(this.configuration.QuotesDirectory, company.Ticker + quotesFileExtension);

			return quotesFileName;
		}

		public IList<Quote> GetQuotes(Company company, bool forceDownload = true)
		{
			if (!this.quotesInfo.Any(p => p.Company == company))
			{
				if (forceDownload)
				{
					lock (QuotesRepository.locker)
					{
						if (!this.QuotesFileAlreadyExists(company))
						{
							this.DownloadQuotes(company);
						}
					} 
				}
				else
				{
					this.quotesInfo.Add(new QuotesInfo() 
					{
 						Company = company,
						Quotes = Enumerable.Empty<Quote>().ToList()
					});
				}
			}

			var quotes = this.quotesInfo.SingleOrDefault(info => info.Company == company).Quotes;

			return quotes;
		}

		public Quote GetQuote(Company company, DateTime date, bool tryToGetClosest, bool forceDownload = true)
		{
			var quotes = this.GetQuotes(company, forceDownload);

			if (!quotes.Any())
			{
				return null;
			}

			var quote = quotes.SingleOrDefault(q => q.Date.Date == date.Date);

			if (quote == null && tryToGetClosest)
			{
				quote = quotes
					.Where(q => Math.Abs((q.Date - date).TotalDays) == quotes.Select(qt => Math.Abs((qt.Date - date).TotalDays)).Min())
					.FirstOrDefault();
			}

			return quote;
		}
	}
}